package top.yudoge.dogeeditorserverj.service;

import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@Service
public class BagOfWordTextSimilarityService implements TextSimilarityService {

    @Override
    public double similarity(String text1, String text2) {
        if (text1 == null || text2 == null) {
            throw new NullPointerException("text cannot be null");
        }
        if (text1.length() == 0 && text2.length() != 0) return 0;
        if (text1.length() != 0 && text2.length() == 0) return 0;

        List<String> tokenize1 = tokenize(text1);
        log.debug("text1 tokenized: {}", tokenize1);
        List<String> tokenize2 = tokenize(text2);
        log.debug("text2 tokenized: {}", tokenize2);

        Map<String, Integer> text1WordCount = new HashMap<>();
        for (String word : tokenize1) {
            text1WordCount.put(word, text1WordCount.getOrDefault(word, 0) + 1);
        }
        Map<String, Integer> text2WordCount = new HashMap<>();
        for (String word : tokenize2) {
            text2WordCount.put(word, text2WordCount.getOrDefault(word, 0) + 1);
        }

        Set<String> vocabulary = new HashSet<>();
        vocabulary.addAll(tokenize1); vocabulary.addAll(tokenize2);

        int[] vector1 = new int[vocabulary.size()];
        int[] vector2 = new int[vocabulary.size()];

        int i = 0;
        for (String word : vocabulary) {
            vector1[i] = text1WordCount.getOrDefault(word, 0);
            vector2[i] = text2WordCount.getOrDefault(word, 0);
            i++;
        }

        log.debug("vector1: {}", vector1);
        log.debug("vector2: {}", vector2);

        return calcuSimilarity(vector1, vector2);
    }


    public List<String> tokenize(String text) {
        List<String> strings = new ArrayList<>();

        StringBuilder currToken = new StringBuilder();
        for (char c : text.toCharArray()) {
            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { // 英语 by word、其它，by character
                currToken.append(c);
            } else {
                if (!currToken.isEmpty()) {
                    strings.add(currToken.toString());
                    currToken = new StringBuilder();
                }
                strings.add(String.valueOf(c));
            }
        }
        if (!currToken.isEmpty()) {
            strings.add(currToken.toString());
        }
        return strings;
    }

    public static double calcuSimilarity(int[] vector1, int[] vector2) {
        if (vector1.length != vector2.length)
            throw new IllegalArgumentException("vector1 and vector2 must have same length");

        int n = vector1.length;

        double a = 0;
        for (int i = 0; i < n; i++) {
            a += vector1[i] * vector2[i];
        }

        double b = 0;
        for (int i = 0; i < n; i++) {
            b += vector1[i] * vector1[i];
        }
        b = Math.sqrt(b);

        double c = 0;
        for (int i = 0; i < n; i++) {
            c += vector2[i] * vector2[i];
        }
        c = Math.sqrt(c);

        double cosT = a / (b * c);

        double magnitude1 = magnitude(vector1);
        double magnitude2 = magnitude(vector2);
        double similarity = magnitude1 * magnitude2 * cosT;
        log.debug("{} * {} * {} = {}", magnitude1, magnitude2, cosT, similarity);
        return similarity;
    }

    private static double magnitude(int[] vector) {
        double sum = 0;
        for (int i = 0; i < vector.length; i++) {
            sum += vector[i] * vector[i];
        }
        return Math.sqrt(sum);
    }

}
